package org.wei.bean.manager;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.wei.xml.bean.Bean;
import org.wei.xml.bean.Factory;
import org.wei.xml.bean.Param;
import org.wei.xml.bean.Property;

public class BeanManager {

	private static Map<String, Bean> xml2beanMap = new HashMap<>();
	private static BeanManager instance;
	private static Map<String, Object> singleBeanMap = new HashMap<>();

	@SuppressWarnings("unchecked")
	private BeanManager() {
		try {
			SAXReader sr = new SAXReader();
			Document doc = sr.read("src/beans.xml");
			Element root = doc.getRootElement();
			Iterator<Element> it = root.elementIterator();
			while (it.hasNext()) {
				Element beanElement = it.next();
				Bean bean = element2bean(beanElement);
				xml2beanMap.put(bean.getId(), bean);
			}
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
	}

	@SuppressWarnings("unchecked")
	private Bean element2bean(Element beanElement) {
		String clazz = beanElement.attributeValue("class");
		String id = beanElement.attributeValue("id");
		boolean single = !"no".equals(beanElement.attributeValue("single"));
		Bean bean = new Bean();
		bean.setClazz(clazz);
		bean.setId(id);
		bean.setSingle(single);
		Iterator<Element> beanIt = beanElement.elementIterator();
		while (beanIt.hasNext()) {
			Element element = beanIt.next();
			if (element.getName().equals("factory")) {
				Factory factory = new Factory();
				factory.setClazz(element.attributeValue("class"));
				factory.setMethod(element.attributeValue("method"));
				Iterator<Element> paramIt = element.elementIterator();
				while (paramIt.hasNext()) {
					Element paramElement = paramIt.next();
					Param param = new Param();
					param.setType(paramElement.attributeValue("type"));
					param.setValue(paramElement.getText());
					factory.addParam(param);
				}
				bean.setFactory(factory);
				break;
			}
			if (element.getName().equals("property")) {
				Property property = new Property();
				property.setName(element.attributeValue("name"));
				property.setRef(element.attributeValue("ref"));
				property.setType(element.attributeValue("type"));
				property.setValue(element.getText());
				bean.addProperty(property);
			}
		}
		return bean;
	}

	public Object getBean(String id) throws Exception {
		Bean bean = xml2beanMap.get(id);
		if (bean.isSingle()) {
			if (singleBeanMap.containsKey(id)) {
				return singleBeanMap.get(id);
			}
			Object obj = createObjectByBean(bean);
			singleBeanMap.put(id, obj);
			return obj;
		}
		return createObjectByBean(bean);
	}

	private Object createObjectByBean(Bean bean) throws Exception {
		Class<?> clazz = Class.forName(bean.getClazz());
		if (bean.getFactory() != null) {
			return createObjectByFactory(bean.getFactory());
		}
		Object obj = clazz.getConstructor(new Class<?>[0]).newInstance(
				new Object[0]);
		List<Property> properties = bean.getProperties();
		for (Property property : properties) {
			String ref = property.getRef();
			String setterName = propertySetterName(property.getName());
			if (ref != null) {
				Bean refBean = xml2beanMap.get(ref);
				Object refObject = getBean(refBean.getId());
				clazz.getMethod(setterName, Class.forName(refBean.getClazz()))
						.invoke(obj, refObject);
				continue;
			}
			String value = property.getValue();
			Method setter = null;
			if (property.getType().equals("int")) {
				setter = clazz.getMethod(setterName, int.class);
				setter.invoke(obj, Integer.parseInt(value));
			} else if (property.getType().equals("long")) {
				setter = clazz.getMethod(setterName, long.class);
				setter.invoke(obj, Long.parseLong(value));
			} else if (property.getType().equals("double")) {
				setter = clazz.getMethod(setterName, double.class);
				setter.invoke(obj, Double.parseDouble(value));
			} else if (property.getType().equals("string")) {
				setter = clazz.getMethod(setterName, String.class);
				setter.invoke(obj, value);
			} else if (property.getType().equals("boolean")) {
				setter = clazz.getMethod(setterName, boolean.class);
				setter.invoke(obj, Boolean.parseBoolean(value));
			} else {
				throw new Exception(
						"bad type attribute,must be int|long|double|string|boolean");
			}
		}
		return obj;
	}

	private Object createObjectByFactory(Factory factory) throws Exception {
		Class<?> factoryClazz = Class.forName(factory.getClazz());
		Class<?>[] paramsType = new Class<?>[factory.getParams().size()];
		Object[] paramsValue = new Object[factory.getParams().size()];
		for (int i = 0; i < factory.getParams().size(); i++) {
			Param param = factory.getParams().get(i);
			if (param.getType().equals("int")) {
				paramsType[i] = int.class;
				paramsValue[i] = Integer.parseInt(param.getValue());
			} else if (param.getType().equals("long")) {
				paramsType[i] = long.class;
				paramsValue[i] = Long.parseLong(param.getValue());
			} else if (param.getType().equals("double")) {
				paramsType[i] = double.class;
				paramsValue[i] = Double.parseDouble(param.getValue());
			} else if (param.getType().equals("string")) {
				paramsType[i] = String.class;
				paramsValue[i] = param.getValue();
			} else if (param.getType().equals("boolean")) {
				paramsType[i] = boolean.class;
				paramsValue[i] = Boolean.parseBoolean(param.getValue());
			} else {
				throw new Exception(
						"bad type attribute,must be int|long|double|string|boolean");
			}
		}
		Method factoryMethod = factoryClazz.getMethod(factory.getMethod(),
				paramsType);
		return factoryMethod.invoke(factoryClazz, paramsValue);
	}

	public static BeanManager getInstance() {
		if (instance == null) {
			instance = new BeanManager();
		}
		return instance;
	}

	private String propertySetterName(String propertyName) {
		StringBuilder sb = new StringBuilder(propertyName);
		sb.setCharAt(0, Character.toUpperCase(sb.charAt(0)));
		sb.insert(0, "set");
		return sb.toString();
	}
}
